/**
 * Licensed under the MIT License
 *
 * Copyright (c) 2010 Alexandre Croiseaux
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package marcel.display.gui.components
{
	import by.blooddy.crypto.serialization.JSON;
	import flash.display.DisplayObject;
	import flash.display.InteractiveObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.filters.GlowFilter;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import marcel.debug.*;
	import marcel.display.gui.form.IFormComponent;
	import marcel.display.Tween;
	import marcel.display.View;
	import marcel.utils.DisplayObjectUtils;
	
	/**
	* Dispatched when the value has changed
	* @eventType flash.events.Event.CHANGE
	*/
	[Event(name = "change", type = "flash.events.Event")]
	/**
	* Dispatched when the list is opened
	* @eventType flash.events.Event.OPEN
	*/
	[Event(name = "open", type = "flash.events.Event")]
	/**
	* Dispatched when the list is closed
	* @eventType flash.events.Event.CLOSE
	*/
	[Event(name = "close", type = "flash.events.Event")]
	
	/**
	 * A ComboBox component class to use in forms.
	 * The graphics can by changed by editing the ListItemRenderer Class.
	 * @author Alexandre Croiseaux
	 */
	public class ComboBox extends View implements IFormComponent
	{
		//--------------------------------------------------------------------------
		//
		//  Public vars
		//
		//--------------------------------------------------------------------------
		/**
		 * Access to the List instance used by this combobox
		 */
		public var list:List;
		
		//--------------------------------------------------------------------------
		//
		//  Private vars
		//
		//--------------------------------------------------------------------------
		/**
		 * @private
		 */
		protected var _header:InteractiveObject;
		/**
		 * @private
		 */
		protected var _opened:Boolean;
		/**
		 * @private
		 */
		protected var _openUp:Boolean;
		private var _listContainer:Sprite;
		
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		/**
		 * Constructs a new ComboBox instance
		 * @param	width	The width of the combobox
		 * @param	numItemsVisible	The number of items visible in the list
		 * @param	openUp	Indicates if the list must be opened over (true) or below the combobox header (false, default)
		 */
		public function ComboBox(width:uint = 150, numItemsVisible:uint = 5, openUp:Boolean = false, rendererClassOrClassName:* = "marcel.display.gui.components.ListItemRenderer", scrollTrackOnly:Boolean = true)
		{
			_listContainer = new Sprite();
			addChild(_listContainer);
			
			list = new List(width, numItemsVisible, rendererClassOrClassName, scrollTrackOnly);
			list.addEventListener(Event.CHANGE, onItemSelected)
			
			createHeader(width);
			_header.addEventListener(MouseEvent.CLICK, onClickHeader);
			
			_openUp = openUp;
			_listContainer.y = openUp ? -list.height : _header.height;
			list.y = openUp ? list.height : -list.height;
			
			_listContainer.scrollRect = new Rectangle(0, 0, width, list.height);
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Public methods
		//
		//--------------------------------------------------------------------------
		/**
		 * Set the text displayed in the combobox header
		 * @param	label	The string text to display
		 */
		public function setLabel(label:String):void
		{
			Object(_header).setLabel(label);
		}
		
		/**
		 * Set the content of the list, as an array of Objects instances containing 'label' and 'data' keys. The 'label' key is used as displayed text into the list.
		 * Ex : [{label:"val 1", data:1}, {label:"val 2", data:2}]
		 */
		public function set data(data:Array):void
		{
			list.data = data;
			updateScrollRect();
		}
		
		/**
		 * Returns the 'data' key of the selected item, or null if no item is selected.
		 * @return the 'data' key of the selected item
		 */
		public function getValue():*
		{
			return list.getValue() ? list.getValue().data : null;
		}
		
		/**
		 * Selects the specified index in the list
		 * @param	oValue	The int index to select, pass -1 to deselect all.
		 */
		public function setValue(oValue:*) : void
		{
			selectedIndex = uint(oValue);
		}
		
		/**
		 * Set the text displayed in the combobox header. This method is an alias of the setLabel() method.
		 */
		public function set defaultValue(value:String):void
		{
			setLabel(value);
		}
		
		/**
		 * Indicates nhe number of items visible in the list
		 */
		public function set numVisibleItems(num:uint):void
		{
			list.numVisibleItems = num;
			updateScrollRect();
		}
		
		/**
		 * Set the content of the list by parsing a JSON string. Use this method to populate the list from a config file or an HTTP parameter.
		 */
		public function set JSONData(value:String):void
		{
			data = JSON.decode(value);
		}
		
		/**
		 * updates the width of the combobox
		 */
		override public function set width(value:Number):void
		{
			_header.width = value;
			list.width = value;
			updateScrollRect();
		}
		
		/**
		 * Indicates if th cursor tracker must bes resized regarding the number of elements
		 */
		public function set resizeTracker(value:Boolean):void
		{
			list.resizeTracker = value;
		}
		
		/**
		 * Selects the specified index in the list
		 * @param	index	The int index to select, pass -1 to deselect all.
		 */
		public function set selectedIndex(index:int):void
		{
			list.selectedIndex = index;
			setLabel(list.getValue().label);
		}
		
		/**
		 * Returns the index of the selected data.
		 */
		public function get selectedIndex():int
		{
			return list.selectedIndex;
		}
		
		/**
		 * Displays the current component as having an error or not. This method is automatically called by the Form class if form.useHighlight is set to true.
		 * @param	hasError	Indicates if there is an error or not.
		 */
		public function displayError(hasError:Boolean):void
		{
			Object(_header).filters = hasError ? [new GlowFilter(0xFF0000, 1, 2, 2, 2, 3, true, false)] : null;
		}
		
		/**
		 * Forces the list to be visible or not
		 * @param	bOpened	true if the list must be visible, false otherwise
		 */
		public function setListOpened(bOpened:Boolean):void
		{
			_opened = bOpened;
			if (bOpened) _listContainer.addChild(list);
			list.getScroller().enableMouseWheel(_opened);
			
			if (_opened) stage.addEventListener(MouseEvent.CLICK, onClickStage);
			else stage.removeEventListener(MouseEvent.CLICK, onClickStage);
			
			var y:int = bOpened ? 0 : -list.height;
			if (_openUp) y = bOpened ? 0 : list.height;
			Tween.moveTo(list, new Point(0, y), .25, null, 0, { onComplete:onTweenComplete } );
			
			list.enableKeyboard(bOpened);
			
			dispatchEvent(new Event(bOpened ? Event.OPEN : Event.CLOSE));
		}
		
		//--------------------------------------------------------------------------
		//
		//  Private methods
		//
		//--------------------------------------------------------------------------
		/**
		 * @private
		 */
		protected function createHeader(width:uint):void
		{
			var c:Class = list.getRendererClass();
			var hea:* = new c();
			hea.width = width;
			hea.transformToComboHeader();
			addChild(hea);
			_header = hea;
		}
		
		private function updateScrollRect():void
		{
			_listContainer.scrollRect = new Rectangle(0, 0, width, list.height);
		}
		
		
		
		//--------------------------------------------------------------------------
		//
		//  Events
		//
		//--------------------------------------------------------------------------
		private function onTweenComplete():void
		{
			if (!_opened && _listContainer.numChildren > 0) _listContainer.removeChild(list);
		}
		
		private function onItemSelected(e:Event):void
		{
			setLabel(list.getValue().label);
			dispatchEvent(e);
			setListOpened(false);
		}
		
		private function onClickHeader(e:MouseEvent):void
		{
			e.stopImmediatePropagation();
			setListOpened(!_opened);
		}
		
		private function onClickStage(e:MouseEvent):void
		{
			if (!DisplayObjectUtils.containsChild(list.getScroller(), e.target as DisplayObject, true)) setListOpened(false);
		}
	}

}